package pwd.allen.plugin;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.Properties;

/**
 * 插件签名：告诉MyBatis当前插件用来拦截哪些对象的哪些方法
 *
 * 可拦截的方法调用
 *  Executor（update、query、flushStatements、commit、rollback、getTransaction、close、isClosed）
 *  ParameterHandler（getParameterObject、setParameters）
 *  ResultSetHandler（handleResultSets、handleOutputParameters）
 *  StatementHandler（prepare、parameterize、batch、update、query）
 *
 */
@Intercepts({
//        @Signature(type = StatementHandler.class, method = "parameterize", args = Statement.class)
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
/**
 * 插件机制就是为目标对象创建代理对象（AOP），多个拦截器会按照插件配置顺序形成多层代理
 * 在四大对象创建的时候，每个创建出来的对像都经过interceptorChain.pluginAll(parameterHandler);
 * 1.获取到所有的Interceptor（拦截器，实现Interceptor接口）
 * 2.调用interceptor.plugin(target);返回target包装后的对象
 *
 * 插件编写：
 *  1.编写Interceptor的实现类
 *  2.使用@Intercepts注解完成插件签名
 *  3.将写好的插件注册到全局配置文件中
 *
 * @author 门那粒沙
 * @create 2019-07-06 21:28
 **/
public class MyPlugin implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(MyPlugin.class);

    /**
     * 回调时机：目标对象的目标方法被执行时
     *
     * 如果有多个插件，则按照配置的从后往前顺序执行
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        logger.info("方法执行前拦截：{}；参数：{}；是否代理：{}", invocation.getMethod(), invocation.getArgs(), Proxy.isProxyClass(statementHandler.getClass()));

        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        BoundSql boundSql = statementHandler.getBoundSql();
        logger.info("sql={}", boundSql.getSql());

        //修改sql
        String sql_new = "/* !HINT({\"ip\":\"127.0.0.1\") */" + boundSql.getSql();
//        Field field = BoundSql.class.getDeclaredField("sql");
//        field.setAccessible(true);
//        field.set(boundSql, sql_new);
        metaObject.setValue("boundSql.sql", sql_new);

        //执行目标方法
        Object proceed = invocation.proceed();
        return proceed;
    }

    /**
     * 回调时机：目标对象被Configuration创建时
     *
     * 用于给插件机会包装目标对象为代理对象
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        //借助Plugin的wrap方法来使用当前Interceptor包装目标对象，该方法会根据注解决定拦截类和方法，如果不是目标方法则直接执行目标方法
        return Plugin.wrap(target, this);
    }

    /**
     * 调用时机：插件创建并注册时
     *
     * 将插件注册时的property属性设置传进来
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("插件配置的属性：" + properties);
    }
}





